🤖 feat: add Mux Extension Platform v1#3255
Conversation
Capture the extension platform glossary, ADRs, and PRD generated from the design grilling session.
---
_Generated with `mux` • Model: `openai:gpt-5.5` • Thinking: `high` • Cost: `729323{MUX_COSTS_USD:-0}`_
<!-- mux-attribution: model=openai:gpt-5.5 thinking=high costs=103.38 -->
Apply six P0 fixes from advisor review and capture supporting language in CONTEXT.md and a new ADR-0005:
- Add v1 Contribution Activation Matrix (separates schema-supported from capability-consumed; clarifies that themes/layouts/runtime presets/commands are inspection-only in v1).
- Mark inspection-only contribution types as Provisional Descriptors so their schemas can evolve without bumping descriptor version.
- Tighten telemetry boundary to Provenance-gated Telemetry (reserved-prefix regex AND bundled-root provenance), with defense-in-depth.
- Make Snapshot Cache feed only the Inspection Path; Capability Path uses the live Snapshot, and cache invalidation includes Global/Project-local Extension State mtimes.
- Clarify Grant Records store the normalized granted set (inferred registration + operational); enablement does not grant registration.
- Drop the manifest icon field; inspection UI uses generic icons.
- Add P0 Acceptance Criteria section before Implementation Decisions.
- Make IPC mutators identify with { rootId, extensionId } and add BundledExtensionRootResolver as a supporting module.
- Replace bun install --no-save assembly with deterministic offline copy/pack.
- Pre-trust project-local discovery is existence-only.
- Add explicit security tests (reserved prefix, provenance-gated telemetry, capability-vs-cache separation, drift across new contribution types) and screenshot/video evidence requirements to dogfood checklist.
- Add ADR-0005 capturing the aggregate security boundary.
---
_Generated with `mux` • Model: `anthropic:claude-opus-4-7` • Thinking: `max` • Cost: `881861{MUX_COSTS_USD:-0}`_
<!-- mux-attribution: model=anthropic:claude-opus-4-7 thinking=max costs=130.54 -->
…r kill switch ServiceContainer now resolves the bundled extension root via detectBundledExtensionRoot() (preferring the assembled tree at build/extensions in dev) and kicks off an initial extensions.reload() so the Settings UI paints on cold start without a hang. SettingsPage gates the Extensions tab on the EXTENSION_PLATFORM experiment so the kill switch hides the section without unmounting downstream sections. Adds the e2e Electron smoke that asserts the Demo Extension card surfaces on first paint, disappears with the kill switch, survives a renderer reload, and reappears on re-enable without a fresh trust/grant prompt.
…cklist) Adds the v1 authoring quickstart + manifest reference, the full v1 telemetry events catalog with provenance-gating notes, and the pre-release dogfood checklist with screenshot/video evidence requirements. Wires the new pages into docs.json navigation and regenerates the built-in mux-docs index so the docs skill surfaces them.
Formatting-only normalization (collapse multi-line argument lists, consistent quoting) across the extension layer plus minor test cleanup left over from US-026/27/28 integration. Marks US-026/27/28 passes:true in tasks/prd.json.
PRD §Permission Model defines bundled Extensions as policy-granted (not user-consented). Before this fix the Demo Extension shipped with `granted: false`, surfaced `Pending re-grant` on the card, and the `mux-extensions` skill never reached `Available` — breaking the "fresh-install, no manual setup" promise of P0 #2. Discovery now treats `isBundled` as activation-granted, and the Registry synthesizes a matching policy Grant Record at permission-calculation time (never persisted, recomputed on every reload, distribution-identity-aligned so drift stays `null` across version bumps). The discovery test that asserted `activated:false` on a bundled root without a grant has been updated to reflect the new contract.
…ry disabled Renderer's useExperimentValue falls back to `enabledByDefault` when PostHog returns no assignment, but the backend's isExperimentEnabled returned `false` in the same scenario — so dev-server / MUX_E2E builds shipped with EXTENSION_PLATFORM (default-on) effectively off. Frontend showed the Extensions tab while the backend reported "No extension roots configured". isExperimentEnabled now uses EXPERIMENTS[id].enabledByDefault as the fallback. Existing tests for default-off experiments still assert false because their definitions are enabledByDefault:false.
…ively The renderer can't reproduce permissionCalculator's canonical SHA-256 without a Node `crypto` import, so both Consent Shortcut paths sent `requestedPermissionsHash: ""`. After persistence the very next reload read `hash !== ""` → driftStatus `permissions-changed` for an already-fresh grant. setGrant now overwrites the hash with hashRequestedPermissions(<live manifest's requestedPermissions>) before persistence; falls back to hashing grantedPermissions when no live snapshot is available (equivalent under v1's all-or-nothing grants). The frontend-side comments are updated to point at the canonical recompute site instead of misleading future readers.
#2) agentSkillsService had zero references to extensionRegistry. Even with the Demo Extension fully Available in the Registry snapshot, its `mux-extensions` skill never reached the slash menu — directly contradicting PRD P0 #2 ("Demo Extension visible end-to-end … exposes the mux-extensions skill via the existing slash menu"). Changes: - Adds 'extension' as a fourth AgentSkillScope value (project > global > extension > built-in precedence so user-authored skills always shadow extension-provided ones). - ExtensionRegistry.getSkillSources() returns the resolved list of Available skill contributions (absolute body path + display metadata) for agentSkillsService to consume. - discoverAgentSkills / discoverAgentSkillsDiagnostics / readAgentSkill accept an optional extensionSkills array; the agentSkills router supplies it from context.extensionRegistry.getSkillSources() on every call so kill-switch flips and reload events take effect immediately. - readAgentSkill reads the extension body via node:fs (extensions live on the host, not the workspace runtime) and returns it with scope:'extension'. - SkillIndicator gains an "Extension" scope group; MuxMessageMetadata scope union widens to include 'extension' so /skill messages from extension skills render correctly. - Adds YAML frontmatter to packages/mux-extension-platform-demo/SKILL.md so parseSkillMarkdown accepts it (the Manifest Validator only needs the body file to exist; the agent-skill consumer needs frontmatter).
The 'Why?' link in the Inferred Registration Permissions header pointed at docs.mux.dev (not our domain) with an anchor that didn't exist. Repoints to https://mux.coder.com/extensions/authoring#permissions where the v1 permissions reference actually lives.
The Shift+? hotkey hint at the top of the Extensions Settings Section was rendered as low-contrast plain text on a transparent ghost button — hard to read against the section background. Wraps the keybind in the same <kbd> styling already used by ExtensionsCheatSheetModal so the trigger visually echoes the cheat sheet itself instead of looking like a comment.
The modal panel was styled with `bg-background-primary`, but globals.css only defines `--color-background` and `--color-background-secondary`. The non-existent token resolved to nothing, so the panel rendered fully transparent — only the dimmed backdrop was visible behind it, and the shortcut list bled through into the section content. Switches to `bg-background-secondary` to match ConsentShortcutModal.
…atform-demo The package shipped under `@mux/extension-platform-demo`, but `@mux` is not Coder's npm scope. Renames the distribution identity to `@coder/mux-extension-platform-demo` (Extension Identity `mux.platformdemo` is unchanged — it lives under the reserved `mux.*` prefix and survives package renames per ADR-0003). Touches: package.json + README + SKILL.md inside the workspace, the docs/extensions/authoring.mdx quickstart, bundled-extensions.ts comment, test fixtures (the assemble + discovery + registry suites all rebuilt against the new path), and the PRD/CONTEXT/prd.json planning artifacts that referenced the old name. Regenerates the embedded mux-docs builtin skill index so `agent_skill_read` surfaces the renamed link.
agentSkillsService.list (the slash menu) merged extension-contributed skills correctly, but the dispatch path that actually invokes a skill (`agentSession.ts` slash resolver, `agent_skill_read`, `agent_skill_read_file`) called readAgentSkill without the extensionSkills source list. Result: the slash menu showed `mux-extensions` but `/mux-extensions` failed with `Agent skill not found: mux-extensions` — the user-reported bug. Wires a single getExtensionSkillSources provider from ExtensionRegistry.getSkillSources() through three layers: - ServiceContainer registers the provider with both WorkspaceService (for AgentSession.sendMessage slash resolution) and AIService (for the tool layer). - WorkspaceService passes the provider into every AgentSession via a new option. - AgentSession reads the live source list per slash invocation; fixes agentSession.ts:5253 to pass extensionSkills to readAgentSkill. - AIService injects extensionSkills into ToolConfiguration on every stream so agent_skill_read / agent_skill_read_file resolve extension skills the same way they resolve project / global / built-in skills. Adds a regression test in agentSession.agentSkillSnapshot.test.ts that exercises a slash invocation against an extension-contributed skill — fails on the pre-fix codepath, passes here.
|
@codex review Addressed the latest findings:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7e686362d8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed the active-root self-heal gap: non-directory/ELOOP project active roots are now removed as stale before lock sync continues, with regression coverage. |
|
Codex Review: Didn't find any major issues. Nice work! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
@codex review Pushed an empty retry commit after the Unit job hit an unrelated React act/concurrent-render flake; no code changes since your last approval. |
|
Codex Review: Didn't find any major issues. 👍 ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
@codex review Pushed a test-harness fix for the Unit CI failure: the analytics hook tests now restore the previous global window/document instead of clearing them for subsequent UI tests. Targeted UI subset and static-check pass locally. |
|
Codex Review: Didn't find any major issues. Can't wait for the next one! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
@codex review Pushed a narrower DOM isolation fix for the Unit CI failure: analytics hook tests now use the shared UI installDom helper so they restore the baseline Happy DOM environment needed by later UI tests. |
|
Codex Review: Didn't find any major issues. Delightful! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
@codex review Follow-up for Unit CI: removed the per-test analytics DOM install/restore entirely. The analytics tests now only import the shared baseline DOM bootstrap, avoiding async global window/document races with other UI test files. |
|
Codex Review: Didn't find any major issues. What shall we delve into next? ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
@codex review Pushed a shared test DOM recovery fix: tests/ui/dom now exposes ensureDomInstalled() and reinstalls the baseline if older hook tests clear window/document after the module bootstrap has already run. The failing hooks/UI order and static-check pass locally. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6cc6596a77
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed the DOM isolation review: analytics and extension palette hook tests now use per-test installDom()/cleanup restoration again, while the shared dom helper only self-heals if an older test had already cleared the baseline before the per-test snapshot. The failing hook/UI order and static-check pass locally. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6a428ea29d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed the active-source integrity finding: roots now verify the materialized active source tree still matches the project lock content hashes before skipping sync, so tampered active views trigger re-sync even when the lockfile fingerprint is unchanged. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8292afdcfb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed the validation failure path: active-source validation is now inside the same guarded project-lock sync path, so malformed locks or validation errors downgrade the project root to inspectable/non-activating instead of escaping roots enumeration or startup. |
|
Codex Review: Didn't find any major issues. Keep it up! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Summary
Adds the Mux Extension Platform v1 behind the
EXTENSION_PLATFORMexperiment and documents the platform's architectural pivot to Extension Modules: extension folders with a singleextension.ts, statically extractable manifests, QuickJS-based discovery/activation, skill-style root precedence, source locks, and Mux-owned trust/capability state. The implementation is now partially refactored toward that model: trusted roots can discover direct child Extension Module folders via staticextension.tsmanifest extraction, local authoring roots use~/.mux/extensions/local, and source-lock schemas now model git/vendored extension sources without carrying trust state.Background
This PR grew out of the need to consolidate Mux's extension surfaces across skills, tools, agents, policies, themes, and future runtime contributions. During review, the design moved away from npm-package identity and repo-stored project approvals toward a Go-modules-like Extension Module model. The updated docs capture that decision, the code hardens the current scaffold so repositories cannot provide security authority, and the latest slices begin moving discovery/root layout/source metadata from package manifests to static Extension Module manifests and locks.
Implementation
extension.tsmanifest extraction forexport const manifest = defineManifest({ ... })or a literal object export, rejecting dynamic manifest values without executing extension code.extension.ts, including folder-name validation,manifest.namemismatch diagnostics, project-local pre-trust no-read behavior, and static capability validation.~/.mux/extensions/localand updatesinitializeUserRootto create that folder instead of a package-rootpackage.json.bun run debug extensions.agent_skill_*tool reads, including hardened skill-body reads that reject symlinks and TOCTOU path swaps.extension.ts) as the target architecture.~/.mux/extensions/project-state/<project-hash>/, not inside the target repository.Validation
make static-checkmake test -j1bun test src/node/extensions/extensionRoots.test.ts src/node/orpc/extensionsRouter.test.ts src/common/extensions/sourceLocks.test.ts src/node/extensions/staticManifestExtractor.test.ts src/node/extensions/extensionDiscoveryService.test.tsbun test src/common/extensions/conflictResolver.test.ts src/common/extensions/permissionCalculator.test.tsbun test src/node/extensions/bundledExtensionsAssemble.test.tsbun test src/node/extensions/projectExtensionStateService.test.tsbun test src/node/orpc/extensionsRouter.test.tsbun test src/cli/debug/extensions.test.tsbun test src/browser/features/Settings/Sections/ExtensionCard.test.tsx src/browser/features/Settings/Sections/ExtensionsSection.test.tsxRisks
This is a large additive subsystem touching startup wiring, settings UI, package assembly, telemetry, and skill discovery. The primary rollback lever is the default-on
EXTENSION_PLATFORMexperiment. The highest remaining architectural risk is that full QuickJS Registration Discovery/Full Activation and git install/store materialization are still follow-up work; the current module-discovery slice intentionally publishes no module-registered skills until that runtime path exists.Pains
This PR required several review and merge cycles: resolving older security findings, integrating concurrent
mainchanges around heartbeat/image-generation skill filtering, aligning extension skill IDs with agent skill schemas, moving project extension state out of repositories after review identified the trust-injection vulnerability, and beginning the package-to-Extension-Module refactor while preserving transitional compatibility.Generated with
mux• Model:openai:gpt-5.5• Thinking:off• Cost:$916.09